package com.media.entrance.websocket;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.media.entrance.model.*;
import com.media.entrance.service.MatchService;
import com.media.entrance.service.MediaResourceManageService;
import com.media.entrance.config.MediaRouterOptions;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.websocket.Session;

import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;

import static com.media.entrance.model.AgentStatusEnum.*;

@Slf4j
@Component
public class AsyncTask {

    @Autowired
    private MediaRouterOptions mediaRouterOptions;
    @Autowired
    private MatchService matchService;
    @Autowired
    private MediaResourceManageService mediaResourceManageService;

    @Async
    public void agentBusyInform(MatchInfo inputInfo){
        MatchAndResourceModel matchInfo = new MatchAndResourceModel();
        BatchResultDataModel data = new BatchResultDataModel();
        BatchResultModel temp;
        log.info("agentStatusChange：current agentid:[" + inputInfo.getRoomId() + "] answered！");
        matchInfo.setDeviceId(inputInfo.getDeviceId());
        matchInfo.setRoomId(inputInfo.getRoomId());
        matchInfo.setAgentId(inputInfo.getAgentId());
        mediaResourceManageService.getMediaServiceResource(matchInfo);
        data.setMatched(matchInfo);
        temp = new BatchResultModel("Matched", "", data, "EventRequestMatchAgent");
        sendBackFromDeviceId(temp, inputInfo.getDeviceId());
    }
    @Async
    public void agentTimeoutRematch(MatchInfo inputInfo) {
        MatchAndResourceModel matchInfo = new MatchAndResourceModel();
        BatchResultDataModel data = new BatchResultDataModel();
        BatchResultModel temp;
        MatchAndResourceModel matchNew = matchService.requestMatchAgentNew(inputInfo.getDeviceId());
        if (matchNew.getAgentId() != null) {
            log.info("agentStatusChange：current agentid answer timeout, but agentid:[" + matchNew.getAgentId() + "] can ring!");
            data.setMatching(matchNew);
            temp = new BatchResultModel("Matching", "agent answer timeout", data, "EventRequestMatchAgent");
            //目前有终端实现振铃10s超时判断
//                    ringTimeoutProcess(inputInfo.getDeviceId());
        } else {
            //匹配如果不成功则将设备重新加入排队第一个，找坐席，极端情况才会走到
            log.info("agentStatusChange：current agentid:[" + inputInfo.getAgentId() + "]answer timeout, but there is no idle agent！");
            matchInfo.setQueueNo(matchService.rePutInQueue(inputInfo.getDeviceId()));
            matchInfo.setQueueLimit(mediaRouterOptions.getMaxWaitingLineLimit());
            data.setUnmatch(matchInfo);
            temp = new BatchResultModel("Unmatch", "agent [" + inputInfo.getAgentId() + "]answer timeout", data, "EventRequestMatchAgent");
        }
        sendBackFromDeviceId(temp, inputInfo.getDeviceId());
    }
    @Async
    @Deprecated
    //当坐席update自己的状态成功时，ring2idle和idle2busy时触发，将匹配结果信息告诉客户的websocket
    public void agentStatusChange(MatchInfo inputInfo) {
        log.info("agentStatusChange：Send ws messge : [" + inputInfo + "] to deviceId : " + inputInfo.getDeviceId());
        MatchAndResourceModel matchInfo = new MatchAndResourceModel();
        BatchResultDataModel data = new BatchResultDataModel();
        BatchResultModel temp;
        switch (inputInfo.getAgentStatus()) {
            case "idle":
                // last status is ring
                // 设备直接进行一次匹配
                log.info("agentStatusChange：current agentid:[" + inputInfo.getAgentId() + "] answer timeout, matching again!");
                MatchAndResourceModel matchNew = matchService.requestMatchAgentNew(inputInfo.getDeviceId());
                if (matchNew.getAgentId() != null) {
                    log.info("agentStatusChange：current agentid:[" + inputInfo.getAgentId() + "] answer timeout, but agentid:[" + matchNew.getAgentId() + "] can ring!");
                    data.setMatching(matchNew);
                    temp = new BatchResultModel("Matching", "agent answer timeout", data, "EventRequestMatchAgent");
                    //目前有终端实现振铃10s超时判断
//                    ringTimeoutProcess(inputInfo.getDeviceId());
                } else {
                    //匹配如果不成功则将设备重新加入排队第一个，找坐席，极端情况才会走到
                    log.info("agentStatusChange：current agentid:[" + inputInfo.getAgentId() + "] answer timeout, but there is no idle agent！");
                    matchInfo.setQueueNo(matchService.rePutInQueue(inputInfo.getDeviceId()));
                    matchInfo.setQueueLimit(mediaRouterOptions.getMaxWaitingLineLimit());
                    data.setUnmatch(matchInfo);
                    temp = new BatchResultModel("Unmatch", "agent answer timeout", data, "EventRequestMatchAgent");
                }
                sendBackFromDeviceId(temp, inputInfo.getDeviceId());
                break;
            case "busy":
                // 考虑极端情况，再设置busy时，客户的ws断开了
                if (deviceIdSessionAvailable(inputInfo.getDeviceId())) {
                    log.info("agentStatusChange：current agentid:[" + inputInfo.getRoomId() + "] answered！");
                    matchInfo.setDeviceId(inputInfo.getDeviceId());
                    matchInfo.setRoomId(inputInfo.getRoomId());
                    matchInfo.setAgentId(inputInfo.getAgentId());
                    mediaResourceManageService.getMediaServiceResource(matchInfo);
                    data.setMatched(matchInfo);
                    temp = new BatchResultModel("Matched", "", data, "EventRequestMatchAgent");
                    sendBackFromDeviceId(temp, inputInfo.getDeviceId());
                } else {
                    // 释放资源
                    mediaResourceManageService.releaseMediaServiceResource(inputInfo.getRoomId());
                    // 恢复坐席状态为idle
                    matchService.updateAgentRingOrBusyToIdle(inputInfo.getAgentId());
                }
                break;
            default:
                log.error("agentStatusChange：other status is impossible!");
        }
    }

    @Async
    @Deprecated
//    @Scheduled(fixedDelay = 2000) 3.0接口，目前已废弃
    public void doBatchMatch(){
        // 查询排队的所有设备信息
        Set<String> deviceIds = matchService.findAllDeviceInQueue();
        if (deviceIds == null || deviceIds.size() == 0){
            log.info("doBatchMatch: no request device!");
            return;
        }
        List<MatchAndResourceModel> listResult = matchService.scheduleDoBatchMatch(deviceIds);
        if (listResult == null) {
            return;
        }
        // 开个线程池同时更新
        for (MatchAndResourceModel info: listResult) {
            BatchResultDataModel data = new BatchResultDataModel();
            BatchResultModel resultModel;
            if (info.getQueueNo() == null) {
                data.setMatching(info);
                resultModel = new BatchResultModel("Matching", "", data, "EventRequestMatchAgent");
                //目前有终端实现振铃10s超时判断
//                ringTimeoutProcess(info.getDeviceId());
            } else {
                data.setUnmatch(info);
                resultModel = new BatchResultModel("Unmatch", "", data, "EventRequestMatchAgent");
            }
            sendBackFromDeviceId(resultModel, info.getDeviceId());
        }
    }

    @Async
    //匹配作为异步函数 for websocket
    public void doOneMatch(String deviceId) {
        BatchResultModel temp;
        BatchResultDataModel data = new BatchResultDataModel();
        MatchAndResourceModel matchResult =  matchService.getDeviceIdLastMatchResult(deviceId);
        // 如果已经再busy或者ring了
        if (matchResult != null && matchResult.getReason().equals("busy")) {
            log.info("AsyncTask: doOneMatch: get last result busy");
            mediaResourceManageService.getMediaServiceResource(matchResult);
            data.setMatched(matchResult);
            temp = new BatchResultModel("Matched", "last result", data, "EventRequestMatchAgent");
        } else if (matchResult != null && matchResult.getReason().equals("ring")) {
            log.warn("AsyncTask: doOneMatch: get last result ring");
            // 等待批量任务匹配
            data.setMatching(matchResult);
            temp = new BatchResultModel("Matching", "last result", data, "EventRequestMatchAgent");
        } else {
            log.info("AsyncTask: doOneMatch: get first match");
            // 第一次匹配
            matchResult = matchService.requestMatchAgentNew(deviceId);
            if (matchResult.getQueueNo() == null) {
                // 超过上限的情况
                if (matchResult.getReason() != null && matchResult.getReason().equals("domatch over max queue limit")) {
                    data.setUnmatch(matchResult);
                    temp = new BatchResultModel("fail", matchResult.getReason(), data, "EventRequestMatchAgent");
                } else {
                    data.setMatching(matchResult);
                    temp = new BatchResultModel("Matching", "", data, "EventRequestMatchAgent");
                    //目前有终端实现振铃10s超时判断
                    //ringTimeoutProcess(deviceId);
                }
            } else {
                data.setUnmatch(matchResult);
                temp = new BatchResultModel("Unmatch", "", data, "EventRequestMatchAgent");
            }
        }
        sendBackFromDeviceId(temp, deviceId);
    }

    // 发送结果给指定设备的websocket
    private void sendBackFromDeviceId(BatchResultModel temp, String deviceId) {
        if (temp.getData().getMatching() != null) {
            temp.getData().getMatching().setAgentStatus("Matching(ring)");
            matchService.recordDeviceChangeRecords(temp.getData().getMatching());
        } else if (temp.getData().getMatched() != null) {
            temp.getData().getMatched().setAgentStatus("Matched(busy)");
            matchService.recordDeviceChangeRecords(temp.getData().getMatched());
        } else if (temp.getData().getUnmatch() != null) {
            temp.getData().getUnmatch().setAgentStatus("UnMatched(queue)");
            matchService.recordDeviceChangeRecords(temp.getData().getUnmatch());
        } else {
            log.error("sendBackFromDeviceId return result : [" +  temp + "], impossible to here!!!");
        }
        try {
            // 序列化变成字符串
            String result = new ObjectMapper().writeValueAsString(temp);
            log.info("sendBackFromDeviceId return result : [" +  result + "]");
            Session session = WebSocketServer.getOnlineClientMap().get(deviceId);
            if (session != null && session.isOpen()) {
                session.getAsyncRemote().sendText(result);
            } else {
                // websocket断开了，不需要处理，已经再websocket on close里处理了
                log.warn("sendBackFromDeviceId：websocket is unavailable");
            }
        } catch (JsonProcessingException | IllegalStateException e) {
            log.error("sendBackFromDeviceId：throw error" + temp, e);
        }
    }

    public boolean deviceIdSessionAvailable(String deviceId) {
        Session session = WebSocketServer.getOnlineClientMap().get(deviceId);
        if (session == null || !session.isOpen()){
            matchService.cancelMatch(deviceId);
            return false;
        }
        return true;
    }

    @Async
    public CompletableFuture<MatchAndResourceModel> asyncOneDeviceMatchOneAgent(String deviceId, String agentId) {
        return CompletableFuture.supplyAsync(() -> matchService.oneDeviceMatchOneAgent(deviceId, agentId));
    }

    // 只更新排队中的其他设备的位置
    @Async
    public void informDeviceNewPostionInQueue(String deviceId) {
        MatchAndResourceModel matchInfo = new MatchAndResourceModel();
        matchInfo.setDeviceId(deviceId);
        matchInfo.setQueueNo(matchService.getPostionInQueue(deviceId));
        matchInfo.setQueueLimit(mediaRouterOptions.getMaxWaitingLineLimit());
        BatchResultDataModel data = new BatchResultDataModel();
        data.setUnmatch(matchInfo);
        BatchResultModel resultModel = new BatchResultModel("Unmatch", "update queue", data, "EventRequestMatchAgent");
        sendBackFromDeviceId(resultModel, deviceId);
    }

    private void processMatchResultAndSendBack(String deviceId, String agentId) {
        CompletableFuture<MatchAndResourceModel> asyncResult = asyncOneDeviceMatchOneAgent(deviceId, agentId);
        asyncResult.thenAccept(matchInfo -> {
            BatchResultDataModel data = new BatchResultDataModel();
            BatchResultModel resultModel;
            //string util
            if (matchInfo.getReason() != null) {
                // 写库失败 重新放到队列中，如果设备不为空
                log.error("scheduleTaskDoParallelBatchMatch: match fail reason: " + matchInfo.getReason());
                if (matchInfo.getDeviceId() != null) {
                    data.setUnmatch(matchInfo);
                    resultModel = new BatchResultModel("Unmatch", "", data, "EventRequestMatchAgent");
                } else {
                    return;
                }
            } else if (matchInfo.getAgentId() == null) {
                // 没有匹配到坐席，直接返回结果
                data.setUnmatch(matchInfo);
                resultModel = new BatchResultModel("Unmatch", "", data, "EventRequestMatchAgent");
            } else {
                data.setMatching(matchInfo);
                resultModel = new BatchResultModel("Matching", "", data, "EventRequestMatchAgent");
            }
            log.info("scheduleTaskDoParallelBatchMatch send back to deviceId:[" + deviceId + "] result: " + resultModel);
            sendBackFromDeviceId(resultModel, matchInfo.getDeviceId());
        });
    }

    @Async
    @Scheduled(fixedDelay = 2000)
    //线程池改造批量匹配函数
    public void scheduleTaskDoParallelBatchMatch() {
        // 查询排队的所有设备信息 返回是按顺序的，从小到大
        Set<String> deviceIds = matchService.findAllDeviceInQueue();
        if (deviceIds == null || deviceIds.size() == 0){
//            log.info("scheduleTaskDoParallelBatchMatch: no request device!");
            return;
        }

        // 查询所有空闲坐席信息
        List<MatchInfo> matchInfos = matchService.getAllAgentsByStatus("idle");
        int agentNum = matchInfos.size();
        if (agentNum == 0) {
//            log.info("scheduleTaskDoParallelBatchMatch: no idle agent!");
            return;
        }
        int deviceNum = deviceIds.size();
        log.info("scheduleTaskDoParallelBatchMatch: start matching! idle agent number:[" + matchInfos.size() + "], device number:[" + deviceNum + "]");
        int index = 0;
        // 构建要返回的信息
        for (String deviceId:deviceIds){
            String agentId;
            // 考虑设备比空闲坐席多的情况
            if (index >= matchInfos.size()) {
                agentId = null;
            } else {
                // 要先清队列哦
                matchService.clearQueue(deviceId);
                agentId = matchInfos.get(index++).getAgentId();
            }
            processMatchResultAndSendBack(deviceId,agentId);
        }
    }

    @Async
    @Deprecated
    //匹配作为异步函数 for websocket
    //2.0版本使用的异步任务，每3秒的返回一次，3.0已弃用
    public void doMatch(String deviceId) {
        // 匹配坐席
        MatchAndResourceModel matchInfo = new MatchAndResourceModel();
        matchInfo.setDeviceId(deviceId);
        AgentStatusEnum matchResult = OTHER;
        BatchResultModel temp = null;
        AgentStatusEnum lastResult;
        BatchResultDataModel data;
        while (!matchResult.equals(BUSY)) {
            //获取匹配结果
            // 上次匹配结果
            lastResult = matchResult;
            matchResult = matchService.requestMatchAgent(matchInfo, lastResult);
            switch (matchResult) {
                case OTHER:
                    log.error("doMatch over queue limit" + matchInfo);
                    temp = new BatchResultModel("fail", "domatch over max queue limit", null, "EventRequestMatchAgent");
                    break;
                case IDLE:
                    data = new BatchResultDataModel();
                    matchInfo.setAgentId(null);
                    matchInfo.setRoomId(null);
                    data.setUnmatch(matchInfo);
                    temp = new BatchResultModel("Unmatch", "", data, "EventRequestMatchAgent");
                    break;
                case RING:
                    data = new BatchResultDataModel();
                    matchInfo.setQueueNo(null);
                    matchInfo.setQueueLimit(null);
                    data.setMatching(matchInfo);
                    temp = new BatchResultModel("Matching", "", data, "EventRequestMatchAgent");
                    break;
                case BUSY:
                    data = new BatchResultDataModel();
                    mediaResourceManageService.getMediaServiceResource(matchInfo);
                    matchInfo.setQueueNo(null);
                    data.setMatched(matchInfo);
                    temp = new BatchResultModel("Matched", "", data, "EventRequestMatchAgent");
                    break;
            }
            try {
                String result = new ObjectMapper().writeValueAsString(temp);
                log.info("doMatch return result : [" +  result + "]");
                Session session = WebSocketServer.getOnlineClientMap().get(deviceId);
                if (session.isOpen()) {
                    session.getAsyncRemote().sendText(result);
                } else {
                    // websocket断开了
                    log.warn("websocket is unavailable");
                    break;
                }
            } catch (JsonProcessingException | IllegalStateException e) {
                log.error("doMatch throw error" + matchInfo, e);
                break;
            }

            // 每3s匹配一次
//            try {
//                Thread.sleep(3000);
//            } catch (InterruptedException e) {
//                log.error("doMatch throw delay error" + matchInfo, e);
//                break;
//            }
        }
        if (!matchResult.equals(BUSY)) {
            // 如果不是busy状态离开的循环，大概率是ws断了，所以这里要取消匹配结果，busy状态离开是不能清理匹配结果的
            log.warn("match result is not busy, so need cancel match result!!!");
            matchService.cancelMatch(deviceId);
        }
    }
}

